package com.gitee.l0km.beanfilter.interceptor;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.facebook.swift.codec.ThriftStruct;
import com.gitee.l0km.aocache.annotations.AoWeakCacheable;
import com.gitee.l0km.beanfilter.annotations.AssociatedType;
import com.gitee.l0km.beanfilter.annotations.FilterMeta;
import com.gitee.l0km.beanfilter.core.CodecPhase;
import com.gitee.l0km.com4j.basex.reflection.generics.EnhancedTypeResolver;
import com.gitee.l0km.com4j.basex.reflection.generics.TypeVisitor;

import static com.gitee.l0km.beanfilter.interceptor.DelegateMethodSupport.DELEGATE_METHOD_NAME;
import static com.gitee.l0km.beanfilter.interceptor.DelegateMethodSupport.DELEGATE_METHOD_PARAMETERTYPES;
import static com.gitee.l0km.beanfilter.interceptor.DelegateMethodSupport.delegateMethodAttributesOf;
import static com.gitee.l0km.beanfilter.interceptor.DelegateMethodSupport.getDelegateTypeFrom;
import static com.gitee.l0km.casban.CasbanScanners.findMergedRepeatableAnnotations;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.gitee.l0km.com4j.basex.reflection.generics.TypeResolution.resolveType;
import com.gitee.l0km.common.spring.core.annotation.AnnotatedElementUtils;
import com.gitee.l0km.common.spring.core.annotation.AnnotationAttributes;
import com.gitee.l0km.common.spring.core.annotation.MergedAnnotation;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.primitives.Primitives;

class FilterCompiler {

	/**
	 * 如果输入的方法是代理方法，则返回实际委托的方法<br>
	 * 如果方法定义了 {@code com.gitee.l0km.codegen.annotations.DelegateMethod}注解则根据注解定义的参数返回代理方法，
	 * 否则返回 {@code method}本身
	 * @param method
	 */
	private static Method findDelegateMethodOrSelf(Method method){
		AnnotationAttributes attrs = delegateMethodAttributesOf(method);
		if(attrs.isEmpty()) {
			return method;
		}
		Class<?> delegateType = getDelegateTypeFrom(method, attrs);
		String name = attrs.getString(DELEGATE_METHOD_NAME);
		Class<?>[] parameterTypes = attrs.getClassArray(DELEGATE_METHOD_PARAMETERTYPES);
		try {
			return  delegateType.getMethod(name, parameterTypes);
		} catch (SecurityException e) {
			throw new RuntimeException(e);
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 获取 {@link AssociatedType}注解指定的编码解码阶段的关联类型
	 * @param element
	 * @param phase
	 */
	private static Set<Class<?>> associatedTypesFromAnnotation(AnnotatedElement element, CodecPhase phase){
		Set<AssociatedType> annots = AnnotatedElementUtils.findAllMergedAnnotations(element, AssociatedType.class)
				.stream()
				.filter(a->phase.match(a.phase()))
				.collect(Collectors.toSet());
		FluentIterable<Class<?>> assocatedTypes= FluentIterable.of();
		for(AssociatedType annot:annots) {
			assocatedTypes = assocatedTypes.append(annot.associatedTypes());
			assocatedTypes = assocatedTypes.append(FluentIterable.from(annot.associatedTypeNames()).transform(c->{
				try {
					return Class.forName(c);
				} catch (Exception e) {
					return null;
				}
			}).filter(Predicates.notNull()));
		}
		return assocatedTypes.toSet();
	}
	/**
	 * 获取方法指定的编码解码阶段的关联类型
	 * @param instantiableClass
	 * @param method
	 * @param phase
	 * @see #associatedTypesFromAnnotation(AnnotatedElement, CodecPhase)
	 * @see #associatedBeanTypes(EnhancedTypeResolver, Type...)
	 */
	private static Set<Class<?>> associatedBeanTypes(Class<?> instantiableClass,Method method, CodecPhase phase){
		FluentIterable<Class<?>> associatedBeanTypes = FluentIterable.of();
		if(CodecPhase.DESERIALIZATION.equals(phase)) {
			associatedBeanTypes = associatedBeanTypes.append(associatedBeanTypes(instantiableClass, method.getGenericParameterTypes()));
		}
		if(CodecPhase.SERIALIZATION.equals(phase)) {
			associatedBeanTypes = associatedBeanTypes.append(associatedBeanTypes(instantiableClass, method.getGenericReturnType()));
		}
		Set<Class<?>> associatedBeanTypesFromAnnotation = 
				associatedBeanTypes(instantiableClass, associatedTypesFromAnnotation(method,phase).toArray(new Class[0]));
		if(!associatedBeanTypesFromAnnotation.isEmpty()) {
			FluentIterable<Class<?>> resolvedTypes = FluentIterable.from(associatedBeanTypesFromAnnotation).transform(c->(Class<?>)resolveType(instantiableClass,c));
			associatedBeanTypes = associatedBeanTypes.append(resolvedTypes);
		}
		return associatedBeanTypes.toSet();
	}
	/**
	 * 遍历类中所有方法，为每个方法解析过滤器注解创建过滤器容器对象
	 * @param clazz
	 */
	static Map<Method, FilterContainer> compile(Class<?> clazz) {
		 Map<Method, FilterContainer> containers = Arrays.stream(checkNotNull(clazz,"clazz is null").getMethods())
				 .filter(m->!m.isBridge())
				 .collect(Collectors.toMap(Function.identity(), FilterCompiler::compile));
		 /** 过滤所有容器为为空的方法 */
		return containers.entrySet().stream().filter(entry->!entry.getValue().isEmpty())
				.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
	}
	/**
	 * 从指定方法中解析注解，创建过滤器实例并装入过滤器容器
	 * @param method
	 */
	@AoWeakCacheable
	static FilterContainer compile(Method method) {
		Method delegateMethodOrSelf = findDelegateMethodOrSelf(method);
		Class<?> instantiableClass = getDelegateTypeFrom(method, null);

		FilterContainer filterContainer = new FilterContainer(
				associatedBeanTypes(instantiableClass, delegateMethodOrSelf,CodecPhase.SERIALIZATION),
				associatedBeanTypes(instantiableClass, delegateMethodOrSelf,CodecPhase.DESERIALIZATION));
		Iterable<MergedAnnotation<FilterMeta>> metas = findMergedRepeatableAnnotations(delegateMethodOrSelf, FilterMeta.class);
		try {
			filterContainer.fieldFilterContainer.addFilter(metas);
			filterContainer.valueFilterContainer.addFilter(metas);
		} catch (ReflectiveOperationException e) {
			throw new RuntimeException(e);
		}
		filterContainer.fieldFilterContainer.immutable();
		filterContainer.valueFilterContainer.immutable();
		return filterContainer;
	}
	static Set< Class<?>> associatedBeanTypes(Class<?> instantiableClass, Type... types){
		Visitor visitor= new Visitor(instantiableClass);
		visitor.visit(types);
		return ImmutableSet.copyOf(visitor.beanTypes);
	}
	static class Visitor extends TypeVisitor {

		final Set<Class<?>> beanTypes;
		final Class<?> instantiableClass;

		Visitor(Class<?> instantiableClass) {
			this(new LinkedHashSet<>(), instantiableClass);
		}
		Visitor(Set<Class<?>> beanTypes, Class<?> instantiableClass) {
			this.beanTypes = checkNotNull(beanTypes,"beanTypes is null");
			this.instantiableClass = checkNotNull(instantiableClass,"instantiableClass is null");
		}
		@Override
		protected void visitClass(Class<?> t) {
			PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(t);
			/** 判断是否为Java Bean类型 */
			if(propertyDescriptors.length > 0) {
				beanTypes.add(t);
				for(PropertyDescriptor descriptor: propertyDescriptors) {
					/** 将Java Bean成员类型加入检查 */
					Method readMethod = descriptor.getReadMethod();
					Method writeMethod = descriptor.getWriteMethod();
					if(null != readMethod && null!=writeMethod) {
						visit(resolveType(t,readMethod.getGenericReturnType()));
					}
				}
			}
		}
		@Override
		protected void visitTypeVariable(TypeVariable<?> t) {
			visit(resolveType(instantiableClass, t));
		}
		@Override
		protected void visitWildcardType(WildcardType t) {
		}
		private static boolean isThriftStruct(Class<?> clazz) {
			return null != clazz.getAnnotation(ThriftStruct.class);
		}

		private static final PropertyDescriptor[] EMPTY_DESCRIPTORS = new PropertyDescriptor[0];
		@AoWeakCacheable(expireAfterAccess = 20, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
		private static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) {
			if(isThriftStruct(clazz)) {
				return getPropertyDescriptors0(clazz);
			}
			if(Primitives.unwrap(clazz).isPrimitive()) {
				return EMPTY_DESCRIPTORS;
			}
			String className = clazz.getName();
			className = className.replace('$', '.');

			if (className.startsWith("java.")) {
				return EMPTY_DESCRIPTORS;
			}

			if (clazz.isEnum()) {
				return EMPTY_DESCRIPTORS;
			} else if (clazz.isArray()) {
				return EMPTY_DESCRIPTORS;
			} else if (Annotation.class.isAssignableFrom(clazz)) {
				return EMPTY_DESCRIPTORS;
			} else if (Iterable.class.isAssignableFrom(clazz)) {
				return EMPTY_DESCRIPTORS;
			} else if (Map.class.isAssignableFrom(clazz)) {
				return EMPTY_DESCRIPTORS;
			}else if (Multimap.class.isAssignableFrom(clazz)) {
				return EMPTY_DESCRIPTORS;
			}

			return getPropertyDescriptors0(clazz);
		}
		private static PropertyDescriptor[] getPropertyDescriptors0(Class<?>beanClass) {
			BeanInfo beanInfo;
			try {
				beanInfo = Introspector.getBeanInfo(beanClass);
				
				PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
				if (descriptors == null) {
					descriptors = new PropertyDescriptor[0];
				}
				return descriptors;
			} catch (IntrospectionException e) {
				throw new RuntimeException(e);
			}
		
		}
	}
}
